#include "Camera.h"
#include "stdafx.h"
#include "scene.h"

#include "mat.h"
#include "vec.h"


void Camera::LookAt(const vec3& eye, const vec3& at, const vec3& up) {
	vec3 n,u,v;
	this->placeInWorld = vec4(eye.x,eye.y,eye.z,1);
	n = normalize(eye-at); // OK, we set up the vpn... good...
	u=normalize(cross(up,n));
	v = normalize(cross(n,u));
	vec4 n_ext = vec4(n.x,n.y,n.z,0);
	vec4 u_ext = vec4(u.x,u.y,u.z,0);
	vec4 v_ext = vec4(v.x,v.y,v.z,0);
	vec4 t = vec4(0,0,0,1);
	// this is the already transposed rotation matrix...
	mat4 rotator = mat4(u_ext,v_ext,n_ext,t);
	mat4 translator = Translate(-eye);
	this->cTransform = rotator*translator;
	// now we have to calculate the Transpose(M) matrix...
	this->toWorldTransformer = transpose(mat4(u_ext,v_ext,n_ext,vec4(eye.x,eye.y,eye.z,1)));

	m_eye = eye;
	m_at = at;
	m_up = up;
}

void Camera::Ortho( const GLfloat left, const GLfloat right,
		const GLfloat bottom, const GLfloat top,
		const GLfloat zNear, const GLfloat zFar ) {

	m_is_ortho = true;
	m_left = left;
	m_right = right;
	m_top = top;
	m_bottom = bottom;
	m_zNear = zNear;
	m_zFar = zFar;
	projection = Scale(2/(right-left),2/(top-bottom),2/((-zNear)-(-zFar)))*Translate(-vec3((left+right)/2,(top+bottom)/2,(-zNear-zFar)/2));
}

void Camera::UpdateProjection(const GLfloat & ratio) {
	GLfloat newHeight = (m_right - m_left) / ratio;
	GLfloat center = (m_top + m_bottom)/2;
	if (this->isOrtho()) {
		Ortho(m_left,m_right,center-newHeight/2,center+newHeight/2,m_zNear,m_zFar);
	} else {
		Frustum(m_left,m_right,center-newHeight/2,center+newHeight/2,m_zNear,m_zFar);
	}
}

void Camera::Frustum( 
		const float left, const float right,
		const float bottom, const float top,
		const float zNear, const float zFar ) {
	m_is_ortho = false;
	m_left = left;
	m_right = right;
	m_top = top;
	m_bottom = bottom;
	m_zNear = zNear;
	m_zFar = zFar;
	this->projection = mat4(vec4(2*zNear/(right-left),0,(right+left)/(right-left),0),
								vec4(0,2*zNear/(top-bottom),(top+bottom)/(top-bottom),0),
								vec4(0,0,-(zFar+zNear)/(zFar-zNear),(-2*zFar*zNear)/(zFar-zNear)),
								vec4(0,0,-1,0));
}



void Camera::Perspective( const float fovy, const float aspect, const float zNear, const float zFar) {
	GLfloat bottom = zNear*(tan(-fovy/2)); 
	GLfloat top = zNear * (tan(fovy/2));
	GLfloat width = aspect*(top-bottom);
	GLfloat left = -width/2;
	GLfloat right = width/2;
	Frustum(left,right,bottom,top,zNear,zFar);
}

void Camera::Rotate(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	vec4 a = RotateX(x) * RotateY(y) * RotateZ(z) * vec4(0,0,-1,1);
	a = toWorldTransformer * a;

	vec4 u =toWorldTransformer * RotateX(x) * RotateY(y) * RotateZ(z) * vec4(0,1,0,1);	

	//if(x==0 && z==0){
	//	cout<<"x=z=0, u  = "<<u<<endl;
	//}
	//if(y==0 && z==0){
	//	cout<<"y=z=0, u  = "<<u<<endl;
	//}



	// us is the head of the up vector in world coordinates...
	vec3 u3d = vec3(u.x,u.y,u.z) - m_eye;
	//cout<<"u3d = "<<u3d<<endl;
	LookAt(m_eye, vec3(a.x,a.y,a.z), u3d);
}

void Camera::Move(const GLfloat & x, const GLfloat & y, const GLfloat & z) {
	// no need to change the up vector...
	// only change the eye and the at points...
	vec4 e = toWorldTransformer * Translate(x,y,z) * vec4(0,0,0,1);
	vec4 a = toWorldTransformer * Translate(x,y,z) * vec4(0,0,-1,1);
	LookAt(vec3(e.x,e.y,e.z),vec3(a.x,a.y,a.z),m_up);
}


void Camera::getProjectionDimensions(GLfloat & width, GLfloat & height, GLfloat & depth) {
	width = m_right - m_left;
	height = m_top - m_bottom;
	depth = m_zNear - m_zFar;
}

void Camera::Zoom(GLfloat rate) {
	vec3 projPlaneCenter = vec3((m_left + m_right)/2, (m_top + m_bottom)/2,(m_zNear + m_zFar)/2);
	GLfloat height  = m_top - m_bottom;
	GLfloat width = m_right-m_left;
	height = height / rate;
	width = width / rate;
	// when we perform a zoom, we actually change the projection...
	if (this->isOrtho()) {
		GLfloat depth;
		depth = m_zNear - m_zFar;
		depth = depth/rate;
		// now what we want to do is minimize the cube size...
		Ortho(projPlaneCenter.x - width/2, projPlaneCenter.x + width/2,
			projPlaneCenter.y - height/2, projPlaneCenter.y + height/2,
			projPlaneCenter.z + depth/2,projPlaneCenter.z - depth/2);
	}
	else {
		Frustum(projPlaneCenter.x - width/2, projPlaneCenter.x + width/2,
			projPlaneCenter.y - height/2, projPlaneCenter.y + height/2,
			m_zNear, m_zFar);
	}
}


void Camera::MoveZNear(GLfloat len) {
	// the semantcis of zNear and zFar are different fot Orthographic and Perspective projections...
	GLfloat newZNear = m_zNear + len;
	if (isOrtho()) {
		if (newZNear<=m_zFar) return;
		Ortho(m_left,m_right,m_bottom,m_top,newZNear,m_zFar);
	}
	else {
		if (newZNear <=0) return;
		if (newZNear>=m_zFar) return;
		Frustum(m_left,m_right,m_bottom,m_top,newZNear,m_zFar);
	}
}

void Camera::MoveZFar(GLfloat len) {
	// symmetric to MoveZNear...
	GLfloat newZFar = m_zFar + len;
	if (isOrtho()) {
		if (newZFar>=m_zNear) return;
		Ortho(m_left,m_right,m_bottom,m_top,m_zNear,newZFar);
	}
	else {
		if (newZFar <= m_zNear) return;
		Frustum(m_left,m_right,m_bottom,m_top,m_zNear,newZFar);
	}
}

vec3 Camera::SuggestMountingPoint() const {
	vec4 p;
	if (isOrtho()) {
		p = vec4((m_left+m_right)/2,(m_top+m_bottom)/2,(m_zNear + m_zFar)/2,1);
	}
	else {
		p = vec4((m_left+m_right)/2,(m_top+m_bottom)/2,-(m_zNear + m_zFar)/2,1);
	}
	p =  toWorldTransformer * p;
	return vec3(p.x,p.y,p.z);
}